#include "simodo/lsp-client/LanguageClient.h"
#include "simodo/inout/log/Logger.h"
#include "simodo/inout/token/FileStream.h"
#include "simodo/inout/token/LexicalParameters.h"
#include "simodo/inout/token/Tokenizer.h"
#include "simodo/inout/convert/functions.h"

#include <sstream>

using namespace simodo;
using namespace simodo::inout;

void performOpenCommand(lsp::LanguageClient & client, std::vector<Token> command) 
{
    // format: o uri [grammar]

    if (command.size() < 2) {
        std::cout << "Wrong structure of Open command" << std::endl;
        return;
    }

    std::ifstream in(toU8(command[1].lexeme()));
    if (!in) {
        std::cout << "*** Unable to open file '" << toU8(command[1].lexeme()) << "'" << std::endl;
        return;
    }

    InputStream in_stream(in);
    std::u16string    string_buffer;

    for(;;) {
        char16_t ch = in_stream.get(); 
        if (ch == std::char_traits<char16_t>::eof())
            break; 
        string_buffer += ch;
    }

    client.exec("textDocument/didOpen", variable::Object {{
                    {u"textDocument", variable::Object {{
                        {u"uri",        command[1].lexeme()},
                        {u"languageId", command.size() > 2 ? command[2].lexeme() : u"simodo-script"},
                        {u"version",    0},
                        {u"text",       encodeSpecialChars(string_buffer)},
                    }}},
                }});
}

void performCloseCommand(lsp::LanguageClient & client, std::vector<Token> command) 
{
    // format: c uri

    if (command.size() != 2) {
        std::cout << "*** Wrong structure of Close command" << std::endl;
        return;
    }

    client.exec("textDocument/didClose", variable::Object {{
                    {u"textDocument", variable::Object {{
                        {u"uri",        command[1].lexeme()},
                    }}},
                }});
}

void performChangeCommand(lsp::LanguageClient & client, std::vector<Token> command) 
{
    // format: u uri version text

    if (command.size() != 4) {
        std::cout << "*** Wrong structure of Change command" << std::endl;
        // for(const Token & t : command)
        //     std::cout << "'" << toU8(t.lexeme()) << "': " << toU8(getLexemeTypeName(t.type())) << std::endl;
        return;
    }

    client.exec("textDocument/didChange", variable::Object {{
                    {u"textDocument", variable::Object {{
                        {u"uri",        command[1].lexeme()},
                        {u"version",    int64_t(std::stol(toU8(command[2].lexeme())))},
                    }}},
                    {u"contentChanges", variable::Array {{
                        variable::Object {{{u"text", command[3].lexeme()}}},
                    }}},
                }});
}

void performHoverCommand(lsp::LanguageClient & client, std::vector<Token> command) 
{
    // format: h uri line character

    if (command.size() < 4) {
        std::cout << "*** Wrong structure of Hover command" << std::endl;
        return;
    }

    client.exec("textDocument/hover", 
                variable::Object {{
                    {u"textDocument", variable::Object {{
                        {u"uri", command[1].lexeme()},
                    }}},
                    {u"position", variable::Object {{
                        {u"line", int64_t(std::stol(toU8(command[2].lexeme())))},
                        {u"character", int64_t(std::stol(toU8(command[3].lexeme())))},
                    }}},
                }}, 
                [](const variable::JsonRpc & rpc, const void *){
                    std::cout << "= textDocument/hover => " << variable::toJson(rpc.value()) << std::endl;
                });
}

void performDefinitionCommand(lsp::LanguageClient & client, std::vector<Token> command)
{
    // format: h uri line character

    if (command.size() < 4) {
        std::cout << "*** Wrong structure of Definition command" << std::endl;
        return;
    }

    client.exec("textDocument/definition",
                variable::Object {{
                    {u"textDocument", variable::Object {{
                        {u"uri", command[1].lexeme()},
                    }}},
                    {u"position", variable::Object {{
                        {u"line", int64_t(std::stol(toU8(command[2].lexeme())))},
                        {u"character", int64_t(std::stol(toU8(command[3].lexeme())))},
                    }}},
                }},
                [](const variable::JsonRpc & rpc, const void *){
                    std::cout << "= textDocument/definition => " << variable::toJson(rpc.value()) << std::endl;
                });
}

void performDocSymbolsCommand(lsp::LanguageClient & client, std::vector<Token> command) 
{
    // format: s uri

    if (command.size() != 2) {
        std::cout << "*** Wrong structure of 'documentSymbol' command" << std::endl;
        return;
    }

    client.exec("textDocument/documentSymbol", 
                variable::Object {{
                    {u"textDocument", variable::Object {{
                        {u"uri", command[1].lexeme()},
                    }}},
                }}, 
                [](const variable::JsonRpc & rpc, const void *){
                    std::cout << "= textDocument/documentSymbol => " << variable::toJson(rpc.value()) << std::endl;
                });
}

void performSemTokensCommand(lsp::LanguageClient & client, std::vector<Token> command) 
{
    // format: t uri

    if (command.size() != 2) {
        std::cout << "*** Wrong structure of 'semanticTokens' command" << std::endl;
        return;
    }

    client.exec("textDocument/semanticTokens/full", 
                variable::Object {{
                    {u"textDocument", variable::Object {{
                        {u"uri", command[1].lexeme()},
                    }}},
                }}, 
                [](const variable::JsonRpc & rpc, const void *){
                    std::cout << "= textDocument/semanticTokens => " << variable::toJson(rpc.value()) << std::endl;
                });
}

void performSegmentationFaultCommand(lsp::LanguageClient & client, std::vector<Token> command) 
{
    // format: sf uri

    if (command.size() != 2) {
        std::cout << "*** Wrong structure of 'SegmentationFault' command" << std::endl;
        return;
    }

    client.exec("SegmentationFault", 
                variable::Object {{
                    {u"textDocument", variable::Object {{
                        {u"uri", command[1].lexeme()},
                    }}},
                }}, 
                [](const variable::JsonRpc & rpc, const void *){
                    std::cout << "= SegmentationFault => " << variable::toJson(rpc.value()) << std::endl;
                });
}

void performWaitCommand(lsp::LanguageClient & , std::vector<Token> command) 
{
    // format: w mills

    if (command.size() < 2 || command[1].type() != LexemeType::Number) {
        std::cout << "*** Wrong structure of Wait command" << std::endl;
        return;
    }

    int wait_mills = std::stol(toU8(command[1].lexeme()));
    std::this_thread::sleep_for(std::chrono::milliseconds(wait_mills));
}

int main(int argc, char * argv[]) 
{
    if (argc < 3) {
        std::cout << "Usage: language-client <command file> <path to language server> {<language server args...>}" << std::endl;
        return 1;
    }

    std::ifstream in(argv[1]);

    if (!in) {
        std::cout << "*** Unable to open file '" << argv[1] << "'" << std::endl;
        return 1;
    }

    Logger log(std::cout, "LSP-client", Logger::SeverityLevel::Warning);

    std::vector<std::string> args(argv+3,argv+argc);

    lsp::LanguageClient client(argv[2], args, {}, log);

    if (!client.running()) {
        std::cout << "*** Server don't running" << std::endl;
        return 1;
    }

    client.registerListener("textDocument/publishDiagnostics", [&](variable::JsonRpc rpc, const void *){
        std::cout << "= textDocument/publishDiagnostics => " << variable::toJson(rpc.value()) << std::endl;

        if (rpc.params().isObject()) {
            const variable::Value & uri_value = rpc.params().getObject()->find(u"uri");
            if (uri_value.isString()) {
                const std::u16string uri = uri_value.getString();
                const inout::Token   uri_token {inout::LexemeType::Id, uri, inout::null_token_location};

                performDocSymbolsCommand(client, {inout::null_token, uri_token});
                performSemTokensCommand(client, {inout::null_token, uri_token});
            }
            else
                std::cout << "*** uri not found" << std::endl;
        }
        else
            std::cout << "*** params not found" << std::endl;
    });

    std::string line;
    while(std::getline(in,line) && !line.empty()) {
        std::vector<Token>   command;
        std::istringstream   str_in(line);
        InputStream          in(str_in);
        LexicalParameters    lex;
        lex.masks.push_back({ BUILDING_NUMBER, LexemeType::Number, 10 });
        lex.markups.push_back({u"\"", u"\"", u"\\", LexemeType::Annotation});
        
        Tokenizer            tokenizer(0, in, lex);
        Token                t = tokenizer.getToken();

        while(t.type() != LexemeType::Empty) {
            command.push_back(t);
            t = tokenizer.getToken();
        }

        if (command.empty() || command[0].type() != LexemeType::Id) {
            std::cout << "*** Wrong command" << std::endl;
            continue;
        }

        if (command[0].lexeme() == u"o")
            performOpenCommand(client, command);
        else if (command[0].lexeme() == u"c")
            performCloseCommand(client, command);
        else if (command[0].lexeme() == u"u")
            performChangeCommand(client, command);
        else if (command[0].lexeme() == u"h")
            performHoverCommand(client, command);
        else if (command[0].lexeme() == u"go")
            performDefinitionCommand(client, command);
        else if (command[0].lexeme() == u"w")
            performWaitCommand(client, command);
        // Запускается автоматически при получении диагностики
        // else if (command[0].lexeme() == u"s")
        //     performDocSymbolsCommand(client, command);
        // else if (command[0].lexeme() == u"t")
        //     performSemTokensCommand(client, command);
        else if (command[0].lexeme() == u"sf")
            performSegmentationFaultCommand(client, command);
        else {
            std::cout << "*** Command don't recognized" << std::endl;
            continue;
        }

        if (!client.running()) {
            std::cout << "*** Server don't running" << std::endl;
            return 1;
        }
    }

    // std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    // client.done(1000);
    std::this_thread::sleep_for(std::chrono::milliseconds(5000));

    if (client.ok()) {
        std::cout << "Done" << std::endl;
        return 0;
    }
    else {
        std::cout << "Something is wrong" << std::endl;
        return 1;
    }
}
